Org Mode date arithmetic
Posted: - Modified: | emacs, orgWhenever I need to get Emacs to prompt me for a date or time (even for non-Org things), I use org-read-date
. I love its flexibility. It’s great to be able to say things like +3
for three days from now, fri
for next Friday, +2tue
for two Tuesdays from now, +1w
for next week, and +1m
for next month. It’s easy to use org-read-date
in Emacs Lisp. Calling it with (org-read-date)
(usually as an interactive argument, like so: (interactive (list (org-read-date)))
) gives me a date like 2015-08-06
depending on what I type in.
I use org-read-date
for non-interactive date calculations, too. For example, if I want to quickly get the Org-style date for tomorrow, I can use org-read-date
‘s third parameter (from-string
) like this:
(org-read-date nil nil "+1")
Here’s how to calculate relative dates based on a specified date. You can hard-code the base date or use another org-read-date
to get it. In this example, I’m getting the Monday after 2015-08-31
. Note the use of two +
signs instead of just one.
(org-read-date nil nil "++mon" nil (org-time-string-to-time "2015-08-31"))
org-time-string-to-time
converts a date or time string into the internal representation for time. You can then extract individual components (ex: month) with decode-time
, or convert it to the number of seconds since the epoch with time-to-seconds
. Alternatively, you can convert Org time strings directly to seconds with org-time-to-seconds
.
If you’re working with days, you can convert time strings with org-time-string-to-absolute
. For example, you can use this to calculate the number of days between two dates (including the first day but excluding the last day), like this:
(let ((start-date (org-read-date))
(end-date (org-read-date)))
(- (org-time-string-to-absolute end-date)
(org-time-string-to-absolute start-date)))
To get the month, day, and year, you can use org-time-string-to-time
and decode-time
, or you can use org-time-string-to-seconds
and calendar-gregorian-from-absolute
.
To convert internal time representations into Org-style dates, I tend to use (format-time-string "%Y-%m-%d" ...)
. encode-time
is useful for converting something to the internal time representation. If you’re working with absolute days, you can convert them to Gregorian, and then format the string.
So, to loop over all the days between the start and end date, you could use a pattern like this:
(let* ((start-date (org-read-date)) (end-date (org-read-date)) (current (org-time-string-to-absolute start-date)) (end (org-time-string-to-absolute end-date)) gregorian-date formatted-date) (while (< current end) (setq gregorian-date (calendar-gregorian-from-absolute current)) (setq formatted-date (format "%04d-%02d-%02d" (elt gregorian-date 2) ; month (elt gregorian-date 0) ; day (elt gregorian-date 1))) ; year ;; Do something here; ex: (message "%s" formatted-date) ;; Move to the next date (setq current (1+ current))))
Alternatively, you could use org-read-date
with a default date, like this:
(let* ((start-date (org-read-date)) (end-date (org-read-date)) (current start-date)) (while (string< current end-date) ;; Do something here; ex: (message "%s" current) ;; Move to the next date (setq current (org-read-date nil nil "++1" nil (org-time-string-to-time current)))))
There are probably more elegant ways to write this code, so if you can think of improvements, please feel free to share.
Anyway, hope that helps!
2 comments
Nagora
2015-08-06T17:45:24ZThank you! If I use shift left-arrow, for example, so that a week-based clocktable is showing "thisweek-1" at the top, the caption line says something like: "Clock summary at [2015-08-06 Thu 18:33], for week 2015-W32" which is of no use to me at all, especially given that the very, very, very few times I need to work with year-weeks, they need to start on April 6th for the UK tax year, not the calendar year.
It's been driving me nuts every time my line manager asks for time spent on tasks by week - I have all the information in Org but it always takes me ages to work out which week I need to ask Org for!
Your (interactive (list (org-read-date))) snippet as solved my problem. Hooray!
Alex
2019-02-04T18:19:19ZThank you, this gave me the snippets of code I needed to implement some handy EDNA triggers.